using System;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Security.Cryptography;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using ZedGraph;

public class FileWriter
{
    public FileInfo fi;
    public Stream outputStream;
    public Object slot;

    public FileWriter(FileInfo fi, Stream outputStream, Object slot)
    {
        this.fi = fi;
        this.outputStream = outputStream;
        this.slot = slot;
    }

    public virtual void writeSingle()
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        FileHeaderFactory factory = new FileHeaderFactory();
        FileHeader header = factory.nieuw(fi);
        header.compressionType = 0;
        MyFileStream inputFile = new MyFileStream(fi.FullName, FileMode.Open, FileAccess.Read);
        Stall bogus = new Stall();
        bogus.stall();
        header.md5sum = inputFile.md5().md5sum;
        inputFile.Seek(0, SeekOrigin.Begin);
        MemoryStream headerStream = header.getStream();

        lock (slot)
        {
            outputStream.Write(headerStream.ToArray(), 0, (int)headerStream.Length);
            inputFile.CopyTo(outputStream);
            outputStream.Flush();
        }

        inputFile.Close();
        Console.Error.WriteLine("{0} completed by {1} in {2}", fi.ToString(), Thread.CurrentThread.Name, sw.Elapsed);
    }
}

public class ArchiveFactory
{
    protected PrtHeader prtHeader;
    protected string fileName;
    protected List<string> files;
    BackgroundWorker worker;
    protected int completedCount;
    Object thisLock = new Object();
    public Pool pool;

    public ArchiveFactory() : this(null, null) { }
    public ArchiveFactory(BackgroundWorker worker) : this(worker, 5) { }
    public ArchiveFactory(BackgroundWorker worker, int threads) : this(worker, new Pool(threads)) { }

    public ArchiveFactory(BackgroundWorker worker, Pool pool)
    {
        completedCount = 0;
        this.worker = worker;
        this.pool = pool;
    }

    public Pool getPool()
    {
        return pool;
    }

    public void currentState()
    {
        int percentage = (completedCount * 100) / files.Count;

        if (worker != null)
            worker.ReportProgress(percentage);
    }

    public void createNew(string fileName, List<string> files)
    {
        using (FileStream fs = new FileStream(fileName, FileMode.Create))
        {
            createNew(fs, files);
        }
    }

    public void createNew(Stream outputStream, List<string> files)
    {
        prtHeader = new PrtHeader();
        outputStream.Write(prtHeader.binary(), 0, 3);
        this.files = files;
        write(outputStream);
    }

    public virtual void write(Stream outputStream)
    {
        Object slot = new Object();

        for (int i = 0; i < files.Count; i++)
        {
            FileWriter fw = new FileWriter(new FileInfo(files[i]), outputStream, slot);
            Action fileAction = (() =>
            {
                fw.writeSingle();
            });
            pool.QueueTask(() => fileAction());
        }

        pool.quit();
        Console.Error.WriteLine("close");
        outputStream.Close();
    }

    public void write()
    {
        FileStream fs = new FileStream(fileName, FileMode.Create);
        fs.Write(prtHeader.binary(), 0, 3);
        write(fs);
        fs.Close();
    }

    public Archive load(string fileName)
    {
        Archive archive = new Archive();
        archive.read(new BinaryReader(File.Open(fileName, FileMode.Open)));
        return archive;
    }

    public Archive load(Stream archiveStream)
    {
        Archive archive = new Archive();
        archive.read(new BinaryReader(archiveStream));
        return archive;
    }
}

public class MyMemoryStream2 : MyMemoryStream
{
    public MyMemoryStream getDNGSStream()
    {
        MyMemoryStream ms = new MyMemoryStream();
        DNGSStreamReader reader = new DNGSStreamReader(this);
        Seek(0, SeekOrigin.Begin);
        return reader.readAll();
    }

    public MyMemoryStream compress()
    {
        Seek(0, SeekOrigin.Begin);
        Seek(0, SeekOrigin.Begin);
        MyMemoryStream ms = new MyMemoryStream();
        DNGSStreamWriter writer = new DNGSStreamWriter(ms);
        Seek(0, SeekOrigin.Begin);
        writer.writeAll(this);
        return ms;
    }

    public bool isCompressable()
    {
        Seek(0, SeekOrigin.Begin);

        for (int i = 0; i < this.Length; i++)
        {
            int onzin = ReadByte();

            if (onzin > 127)
                return false;
        }

        Seek(0, SeekOrigin.Begin);
        return true;
    }
}

public class GZipArchiveFactory : ArchiveFactory
{
    private Object slot = new Object();

    public GZipArchiveFactory(BackgroundWorker worker) : base(worker) { }

    public void write()
    {
        BinaryWriter writer = new BinaryWriter(File.Open(fileName, FileMode.Create));
        writer.Write(prtHeader.binary());
        int activeThreads = 0;

        Parallel.For(0, files.Count, i =>
        {
            activeThreads++;
            Console.Error.WriteLine("ActiveThreads: " + activeThreads);
            FileInfo f = new FileInfo(files[i]);
            FileStream sourceStream = f.OpenRead();
            MemoryStream targetStream = new MemoryStream();
            GZipStream compressionStream = new GZipStream(targetStream, CompressionMode.Compress, true);
            sourceStream.CopyTo(compressionStream);
            compressionStream.Close();
            FileHeaderFactory factory = new FileHeaderFactory();
            FileHeader header = factory.nieuw(f);
            MD5CryptoServiceProvider foo = new MD5CryptoServiceProvider();
            sourceStream.Seek(0, SeekOrigin.Begin);
            foo.ComputeHash(sourceStream);
            header.md5sum = foo.Hash;
            header.compressionType = 1;
            header.compressedSize = targetStream.Length;

            lock (slot)
            {
                writer.Write(header.getStream().ToArray());
                writer.Write(targetStream.ToArray());
            }

            targetStream.Close();
            sourceStream.Close();
            activeThreads--;
            completedCount++;
            currentState();
        });

        writer.Close();

    }
}

public class PrtHeader
{
    public byte[] binary()
    {
        return new byte[] { 0x01, 0x02, 0x03 };
    }
}

public class FileHeaderFactory
{
    public FileHeaderFactory() { }

    public FileHeader nieuw(FileInfo f)
    {
        FileHeader header = new FileHeader();
        header.fileName = f.Name;
        header.compressedSize = f.Length;
        header.realSize = f.Length;
        return header;
    }
}

public class Archive : IDisposable
{
    public BinaryReader reader;
    public string fileName { get; set; }
    public List<string> files { get; set; }
    public PrtHeader prtHeader { get; set; }
    public int count = -1;

    public Archive() { prtHeader = new PrtHeader(); }
    public Iterator getIterator() { return new Iterator(reader); }

    public FileHeaderIterator getFileHeaderIterator()
    {
        return new FileHeaderIterator(reader);
    }

    public FileHandleIterator getFileHandleIterator()
    {
        return new FileHandleIterator(reader);
    }

    public Iterator2 getIterator2()
    {
        return new Iterator2(reader);
    }

    public bool test2(BackgroundWorker worker = null)
    {
        FileHandleIterator it = getFileHandleIterator();
        int teller = 0;
        FileHandle fh;

        if (count == -1)
        {
            FileHeaderIterator it2 = new FileHeaderIterator(reader);
            it2.reset();

            while (it2.next() != null)
            {

            }

            count = it2.count;
        }

        it.reset();

        while ((fh = it.next()) != null)
        {
            if (worker.CancellationPending)
                return false;

            teller++;

            int progress = (teller * 100) / count % 101;

            if (worker != null)
                worker.ReportProgress(progress);

            if (!fh.test2())
                return false;
        }

        return true;
    }

    public void extract(string targetDir)
    {
        FileHandleIterator it = getFileHandleIterator();
        FileHandle current;

        while ((current = it.next()) != null)
            current.save2(targetDir);
    }

    public void read(BinaryReader reader)
    {
        this.reader = reader;
    }

    public void Dispose()
    {
        reader.Close();
    }
}

public class FileHeader
{
    public string fileName;
    public byte compressionType;
    public long compressedSize;
    public const ushort MAGIC = 0x0102;
    public long realSize;
    public byte[] md5sum;

    public FileHeader()
    {
        fileName = "";
        compressionType = 0;
        compressedSize = 0;
        realSize = 0;
        md5sum = new byte[16];
    }

    public ListViewItem toListViewItem()
    {
        return new ListViewItem(new[] {fileName, realSize.ToString(), compressedSize.ToString(), BitConverter.ToString(md5sum)});
    }

    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(fileName);
        sb.Append(": ");
        sb.Append(realSize);
        return sb.ToString();
    }

    public MemoryStream getStream()
    {
        MemoryStream memStream = new MemoryStream();
        BinaryWriter writer = new BinaryWriter(memStream);
        writer.Write(MAGIC);
        writer.Write(fileName);
        writer.Write(compressionType);
        writer.Write(compressedSize);
        writer.Write(realSize);
        writer.Write(md5sum);
        return memStream;
    }
}

public static class LockKlas
{
    public static Object slot = new Object();
}

public class FileHandle
{
    public FileHeader header;
    public long dataPosition;
    public BinaryReader reader;
    public MyByteArray myByteArray;

    public FileHandle(FileHeader header, long position, BinaryReader reader)
    {
        this.header = header;
        this.dataPosition = position;
        this.reader = reader;
    }

    public FileHandle(FileHeader header, BinaryReader reader)
    {
        this.header = header;
        this.reader = reader;
    }

    public void fillByteArray()
    {
        if (header.compressionType == 0)
        {     
            myByteArray = new MyByteArray(reader.ReadBytes((int)header.compressedSize));
        }
        else if (header.compressionType == 1)
        {
            GZipStream decompressor = new GZipStream(reader.BaseStream, CompressionMode.Decompress, true);
            byte[] foo = new byte[header.realSize];
            decompressor.Read(foo, 0, (int)header.realSize);
            myByteArray = new MyByteArray(foo);
        }
        else if (header.compressionType == 2)
        {
            DNGSStreamReader decompressor = new DNGSStreamReader(reader.BaseStream);
            MyMemoryStream foo = decompressor.readAll(header);
            myByteArray = new MyByteArray(foo.ToArray());
            foo.Close();

        }
    }

    public void test3()
    {
        test2();
    }

    public bool test2()
    {
        if (myByteArray == null)
        {
            lock (LockKlas.slot)
            {
                fillByteArray();
            }
        }

        MyMD5Sum checksum = myByteArray.md5();

        if (checksum.compare(new MyMD5Sum(header.md5sum)))
        {
            Console.Error.WriteLine(header.fileName + " OK");
            return true;
        }

        Console.Error.WriteLine(header.fileName + " Corrupt");
        return checksum.compare(new MyMD5Sum(header.md5sum));
    }

    public void save2(string path = ".")
    {
        if (myByteArray == null)
        {
            fillByteArray();
        }

        Stall bogus = new Stall();
        bogus.stall();

        if (!Directory.Exists(path))
        {
            Directory.CreateDirectory(path);
        }

        string targetFileName = path + "/" + header.fileName;

        using (FileStream fs = new FileStream(targetFileName, FileMode.Create))
        {
            fs.Write(myByteArray.deArray, 0, (int)header.realSize);
        }
    }

    public override string ToString()
    {
        return header.ToString();
    }
}

public class FileHandleIterator
{
    public int count = 0;
    BinaryReader reader;

    public FileHandleIterator(BinaryReader reader)
    {
        this.reader = reader;
        reader.ReadBytes(3);    // skip header
    }

    public void reset()
    {
        count = 0;
        reader.BaseStream.Seek(3, SeekOrigin.Begin);
    }

    public FileHandle next()
    {
        count++;
        FileHeaderReader fileHeaderReader = new FileHeaderReader(reader);
        FileHeader fh = fileHeaderReader.read();
        return (fh == null) ? null : new FileHandle(fh, reader);
    }
}

public class FileHeaderIterator
{
    BinaryReader reader;
    FileHeader current = null;
    public int count = 0;

    public FileHeaderIterator(BinaryReader reader)
    {
        this.reader = reader;
        reader.ReadBytes(3);    // skip prt header
    }

    public void reset()
    {
        reader.BaseStream.Seek(3, SeekOrigin.Begin);
    }

    public FileHeader next()
    {
        count++;
        FileHeaderReader fileHeaderReader = new FileHeaderReader(reader);

        if (current != null)
            reader.ReadBytes((int)current.compressedSize);

        current = fileHeaderReader.read();
        return current;
    }
}

public class Iterator
{
    FileHeader current;
    BinaryReader reader;
    public int count = 0;

    public Iterator(BinaryReader reader)
    {
        this.reader = reader;

        for (int i = 0; i < 3; i++)
            reader.Read();
    }

    public void reset()
    {
        reader.BaseStream.Seek(3, SeekOrigin.Begin);
    }

    public bool hasNext()
    {
        long currentPosition = reader.BaseStream.Position;
        FileHeaderReader fileHeaderReader = new FileHeaderReader(reader);

        if (current != null)
            reader.BaseStream.Seek(current.compressedSize, SeekOrigin.Current);

        FileHeader test = fileHeaderReader.read();
        reader.BaseStream.Position = currentPosition;

        if (test != null)
            return true;

        return false;
    }

    public FileHeader next()
    {
        count++;
        FileHeaderReader fileHeaderReader = new FileHeaderReader(reader);

        if (current != null)
            this.reader.BaseStream.Seek(current.compressedSize, SeekOrigin.Current);

        current = fileHeaderReader.read();
        return current;
    }
}

public class Iterator2
{
    BinaryReader reader;
    FileHeader current;

    public Iterator2(BinaryReader reader)
    {
        this.reader = reader;

        for (int i = 0; i < 3; i++)
            this.reader.BaseStream.ReadByte();
    }

    public bool hasNext()
    {
        long currentPosition = reader.BaseStream.Position;
        FileHeaderReader fileHeaderReader = new FileHeaderReader(reader);

        if (current != null)
            this.reader.BaseStream.Seek(current.compressedSize, SeekOrigin.Current);

        FileHeader test = fileHeaderReader.read();
        reader.BaseStream.Position = currentPosition;

        if (test != null)
            return true;

        return false;
    }

    public FileHandle next()
    {
        FileHeaderReader fileHeaderReader = new FileHeaderReader(reader);

        if (current != null)
            this.reader.BaseStream.Seek(current.compressedSize, SeekOrigin.Current);

        current = fileHeaderReader.read();
        return new FileHandle(current, reader.BaseStream.Position, reader);
    }
}

public class FileHeaderReader
{
    BinaryReader reader;

    public FileHeaderReader(BinaryReader reader)
    {
        this.reader = reader;
    }

    public FileHeader read()
    {
        FileHeader header = new FileHeader();
        ushort magic = new ushort();

        try
        {
            magic = reader.ReadUInt16();
        }
        catch (Exception)
        {
            Console.Error.WriteLine("End of file");
        }

        if (magic != FileHeader.MAGIC)
            return null;

        header.fileName = reader.ReadString();
        header.compressionType = reader.ReadByte();
        header.compressedSize = reader.ReadInt64();
        header.realSize = reader.ReadInt64();
        header.md5sum = reader.ReadBytes(16);
        return header;
    }
}

public class MyZedGraph : ZedGraphControl
{
    public List<int> timePassed;
    public PointPairList totalThreads = new PointPairList();
    public PointPairList list2 = new PointPairList();
    public PointPairList size = new PointPairList();
    public PointPairList queue = new PointPairList();
    public PointPairList completedCount = new PointPairList();

    public MyZedGraph()
    {
        timePassed = new List<int>();
        EditButtons = MouseButtons.Left;
        EditModifierKeys = ((Keys)((Keys.Alt | Keys.None)));
        IsAutoScrollRange = IsEnableHEdit = IsEnableVEdit = false;
        IsEnableHPan = IsEnableHZoom = IsEnableVPan = IsEnableVZoom = IsPrintFillPage = IsPrintKeepAspectRatio = true;
        IsScrollY2 = false;
        IsShowContextMenu = true;
        IsShowCopyMessage = true;
        IsShowCursorValues = false;
        IsShowHScrollBar = false;
        IsShowPointValues = false;
        IsShowVScrollBar = false;
        IsSynchronizeXAxes = false;
        IsSynchronizeYAxes = false;
        IsZoomOnMouseCenter = false;
        LinkButtons = System.Windows.Forms.MouseButtons.Left;
        LinkModifierKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Alt | System.Windows.Forms.Keys.None)));
        Location = new Point(12, 240);
        Name = "zg1";
        PanButtons = System.Windows.Forms.MouseButtons.Left;
        PanButtons2 = System.Windows.Forms.MouseButtons.Middle;
        PanModifierKeys = ((Keys)((Keys.Shift | System.Windows.Forms.Keys.None)));
        PanModifierKeys2 = System.Windows.Forms.Keys.None;
        PointDateFormat = "g";
        PointValueFormat = "G";
        ScrollMaxX = 0;
        ScrollMaxY = 0;
        ScrollMaxY2 = 0;
        ScrollMinX = 0;
        ScrollMinY = 0;
        ScrollMinY2 = 0;
        
        Size = new System.Drawing.Size(499, 500);
        TabIndex = 0;
        ZoomButtons = MouseButtons.Left;
        ZoomButtons2 = MouseButtons.None;
        ZoomModifierKeys = Keys.None;
        ZoomModifierKeys2 = Keys.None;
        ZoomStepFraction = 0.1;
    }

    int bar = 0;

    public void foo()
    {
    }

    public void myRefresh()
    {
        GraphPane myPane = GraphPane;
        myPane.Title.Text = "MyThreadPool";
        myPane.XAxis.Title.Text = "Seconden";
        myPane.YAxis.Title.Text = "Aantal";
        totalThreads.Add(bar++, Process.GetCurrentProcess().Threads.Count);
        myPane.CurveList.Clear();
        GraphPane.AddCurve("Total Threads", totalThreads, Color.Red, SymbolType.None);
        GraphPane.AddCurve("BusyWorkers", list2, Color.Blue, SymbolType.None);
        GraphPane.AddCurve("Size", size, Color.Gold, SymbolType.Diamond);
        GraphPane.AddCurve("Queue", queue, Color.Green, SymbolType.None);
        GraphPane.AddCurve("Completed Tasks", completedCount, Color.Brown, SymbolType.None);
        myPane.XAxis.MajorGrid.IsVisible = true;
        myPane.YAxis.Scale.FontSpec.FontColor = Color.Red;
        myPane.YAxis.Title.FontSpec.FontColor = Color.Red;
        myPane.YAxis.MajorTic.IsOpposite = false;
        myPane.YAxis.MinorTic.IsOpposite = false;
        myPane.YAxis.MajorGrid.IsZeroLine = false;
        myPane.YAxis.Scale.Align = AlignP.Inside;
        myPane.Chart.Fill = new Fill(Color.White, Color.LightGray, 45.0f);
        IsShowPointValues = true;
        AxisChange();
        Invalidate();
    }
}









